﻿using System;
using System.Xml.Serialization;

namespace Bambit.XmlRpc
{
	[Serializable]
	[XmlType]
	[XmlRoot("value")]
	public class Value
	{
		#region Class Data
		private string _Text;
		private object _Item;
		#endregion

		/// <summary>
		/// Gets or sets the item.
		/// </summary>
		/// <value>The item is the actual value of this <see cref="Value"/>.</value>
		/// <remarks>A <see cref="Value"/> object can contain EITHER a text node (which will be
		/// stored in it's <see cref="Text"/> property) or a value in it's <see cref="Item"/>
		/// property, not both.
		/// To set or retrieve the value of a <see cref="Value"/> object, it is best to simply
		/// cast the <see cref="Value"/> to the appropriate type, rather than attempt to set the
		/// <see cref="Item"/> or <see cref="Text"/> fields manually.  These properties are only
		/// exposed to ease serialization and will probably be removed when this value is converted
		/// to an <see cref="IXmlSerializable"/> child</remarks>
		[XmlElement("array", typeof(RpcArray))]
		[XmlElement("base64", typeof(byte[]), DataType = "base64Binary")]
		[XmlElement("dateTime.iso8601", typeof(string))]
		[XmlElement("double", typeof(double))]
		[XmlElement("i4", typeof(int))]
		[XmlElement("int", typeof(int))]
		[XmlElement("string", typeof(string))]
		[XmlElement("struct", typeof(RpcStruct))]
		[XmlElement("boolean", typeof(Boolean))]
		[XmlChoiceIdentifier("ItemType")]
		public object Item
		{
			get { return _Item; }
			set
			{
				_Item = value;
				_Text = null;
			}
		}

		/// <summary>
		/// Gets or sets the type of the item.
		/// </summary>
		/// <value>The type of the item.</value>
		/// <remarks>For explination, see <see cref="Item"/></remarks>
		[XmlIgnore]
		public ItemChoiceType ItemType { get; set; }

		/// <summary>	
		/// Gets or sets the text.
		/// </summary>
		/// <value>The text value of this <see cref="Value"/>.</value>
		/// <remarks>See <see cref="Item"/> for more information</remarks>
		[XmlText]
		public string Text
		{
			get
			{
				return _Text;
			}
			set
			{
				ItemType = ItemChoiceType.String;
				_Text = value;
				_Item = null;
			}
		}


		/// <summary>
		/// Performs an explicit conversion of the <see cref="Value"/> to <see cref="RpcStruct"/>.
		/// </summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>The <see cref="RpcStruct"/> object from the data.</returns>
		/// <exception cref="InvalidCastException">Could not convert the value to a <see cref="RpcStruct"/></exception>
		public static explicit operator RpcStruct(Value value) { return (RpcStruct)value.Item; }

		/// <summary>
		/// Performs an explicit conversion of the <see cref="Value"/> to <see cref="RpcArray"/>.
		/// </summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>The a <see cref="RpcArray"/> object from the data.</returns>
		/// <exception cref="InvalidCastException">Could not convert the value to an <see cref="RpcArray"/></exception>
		public static explicit operator RpcArray(Value value) { return (RpcArray)value.Item; }

		/// <summary>
		/// Performs an explicit conversion of the <see cref="Value"/> to Array of <see cref="byte"/>.
		/// </summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>The Array of <see cref="RpcArray"/> object from the data.</returns>
		/// <exception cref="InvalidCastException">Could not convert the value to a an Array of <see cref="byte"/></exception>
		public static explicit operator byte[](Value value) { return (byte[])value.Item; }

		/// <summary>
		/// Performs an explicit conversion  of the <see cref="Value"/> to <see cref="DateTime"/>.
		/// </summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>The a <see cref="DateTime"/> object from the data.</returns>
		/// <exception cref="InvalidCastException">Could not convert the value to a <see cref="DateTime"/></exception>
		public static explicit operator DateTime(Value value)
		{
			string s = (string) value.Item;
			if(s.IndexOf("-")!=4 && s.IndexOf("/")!=4)
			{
				s = string.Format("{0}-{1}-{2}", s.Substring(0, 4), s.Substring(4, 2), s.Substring(6));
			}
				return DateTime.Parse(s);
		}

		/// <summary>
		/// Performs an explicit conversion  of the <see cref="Value"/> to <see cref="Double"/>.
		/// </summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>The a <see cref="Double"/> object from the data.</returns>
		/// <exception cref="InvalidCastException">Could not convert the value to a <see cref="Double"/></exception>
		public static explicit operator Double(Value value) { return (Double)value.Item; }

		/// <summary>
		/// Performs an explicit conversion  of the <see cref="Value"/> to <see cref="Int32"/>.
		/// </summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>The a <see cref="Int32"/> object from the data.</returns>
		/// <exception cref="InvalidCastException">Could not convert the value to a <see cref="Int32"/></exception>
		public static explicit operator Int32(Value value) { return (Int32)value.Item; }


		/// <summary>
		/// Performs an explicit conversion  of the <see cref="Value"/> to <see cref="Int32"/>.
		/// </summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>The a <see cref="Int32"/> object from the data.</returns>
		/// <exception cref="InvalidCastException">Could not convert the value to a <see cref="Int32"/></exception>
		public static explicit operator bool(Value value) { return (bool)value.Item; }

		/// <summary>
		/// Performs an explicit conversion of the<see cref="Value"/> to <see cref="String"/>.
		/// </summary>
		/// <param name="value">The value to convert.</param>
		/// <returns>The a <see cref="String"/> object from the data.</returns>
		/// <exception cref="InvalidCastException">Could not convert the value to a <see cref="String"/></exception>
		public static explicit operator String(Value value)
		{
			return String.IsNullOrEmpty(value.Text) ? (string)value.Item : value.Text;
		}

		/// <summary>
		/// Performs an implicit conversion from <see cref="RpcStruct"/> to <see cref="Bambit.XmlRpc.Value"/>.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <returns>The result of the conversion.</returns>
		public static implicit operator Value(RpcStruct item)
		{
			return new Value { Item = item, ItemType = ItemChoiceType.Struct };
		}

		/// <summary>
		/// Performs an implicit conversion from <see cref="System.String"/> to <see cref="Bambit.XmlRpc.Value"/>.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <returns>The result of the conversion.</returns>
		public static implicit operator Value(string item)
		{
			return new Value { Item = item, ItemType = ItemChoiceType.String };
		}

		/// <summary>
		/// Performs an implicit conversion from <see cref="System.Int32"/> to <see cref="Bambit.XmlRpc.Value"/>.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <returns>The result of the conversion.</returns>
		public static implicit operator Value(int item)
		{
			return new Value { Item = item, ItemType = ItemChoiceType.Int };
		}


		/// <summary>
		/// Performs an implicit conversion from <see cref="System.Double"/> to <see cref="Bambit.XmlRpc.Value"/>.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <returns>The result of the conversion.</returns>
		public static implicit operator Value(Double item)
		{
			return new Value { Item = item, ItemType = ItemChoiceType.Double };
		}

		/// <summary>
		/// Performs an implicit conversion from <see cref="System.DateTime"/> to <see cref="Bambit.XmlRpc.Value"/>.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <returns>The result of the conversion.</returns>
		public static implicit operator Value(DateTime item)
		{
			return new Value { Item = item.ToString("yyyyMMddThh:mm:sszz"), ItemType = ItemChoiceType.DateTime };
		}

		/// <summary>
		/// Performs an implicit conversion from <see cref="System.Byte"/> to <see cref="Bambit.XmlRpc.Value"/>.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <returns>The result of the conversion.</returns>
		public static implicit operator Value(byte[] item)
		{
			return new Value { Item = item, ItemType = ItemChoiceType.Base64 };
		}

		/// <summary>	
		/// Performs an implicit conversion from <see cref="RpcArray"/> to <see cref="Bambit.XmlRpc.Value"/>.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <returns>The result of the conversion.</returns>
		public static implicit operator Value(RpcArray item)
		{
			return new Value { Item = item, ItemType = ItemChoiceType.Array };
		}

		/// <summary>
		/// Performs an implicit conversion from <see cref="RpcArray"/> to <see cref="Bambit.XmlRpc.Value"/>.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <returns>The result of the conversion.</returns>
		public static implicit operator Value(bool item)
		{
			return new Value { Item = item, ItemType = ItemChoiceType.Boolean };
		}


	}
}
